package com.xjd.edu.toolkit.metrics.web;

import com.xjd.edu.toolkit.helper.ExceptionHelper;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;

/**
 * Web异常计数监控
 *
 * @author wangyu
 * @since 2023/3/20
 */
@Slf4j
public class WebErrorCounter {

    final MeterRegistry registry;
    final StandardEnvironment environment;

    /**
     * 由于 @ControllerAdvice | @ExceptionHandler 中无法直接获取 HttpServletRequest 实例，也是要通过这种方式来获取，索性直接在这里注入
     */
    @Autowired(required = false)
    private HttpServletRequest request;

    public WebErrorCounter(MeterRegistry registry, StandardEnvironment environment) {
        this.registry = registry;
        this.environment = environment;
    }

    /**
     * 异常计数，用于无法直接取得 HttpServletRequest 实例情形
     *
     * @param e 非空
     */
    public void count(Throwable e) {
        this.count(this.request, e);
    }

    /**
     * 异常计数
     *
     * @param request 非空
     * @param e       非空
     */
    public void count(HttpServletRequest request, Throwable e) {

        if (request == null) {
            request = this.request;
        }

        // 容错逻辑
        if (request == null || e == null) {
            return;
        }

        if (log.isDebugEnabled()) {
            log.debug("执行Web请求 {} 出错", request.getRequestURI(), e);
        }

        counter(ExceptionHelper.unwrap(e).getClass(), request.getRequestURI()).increment();
    }

    /**
     * 对 counter 实例进行缓存，系统中存在的异常总类型有限，所以这里不会出现内存爆炸的问题
     *
     * @param type 非空
     * @param uri  非空
     * @return
     */
    private Counter counter(Class<? extends Throwable> type, String uri) {
        return registry.counter("game.web.error",
                "uri", uri,
                "exception", type.getName(),
                "type", isBizException(type) ? "biz" : "none");
    }

    static final String SPLIT_REGEX = "\\s*,\\s*";

    /**
     * 判断异常类型是否是业务异常
     *
     * @param type 非空
     * @return
     */
    private boolean isBizException(Class<? extends Throwable> type) {
        // 性能优化：非 com.fulu 开头的异常一定不是业务异常，快速返回
        if (!type.getName().startsWith("com.fulu.")) {
            return false;
        }

        // com.fulu.common.exception 下异常一律视作业务异常
        if (type.getName().startsWith("com.fulu.common.exception.")) {
            return true;
        }

        // 通过配置来设定业务异常
        final String exceptions = environment.getProperty("game.metrics.exception.biz");
        if (!StringUtils.hasText(exceptions)) {
            return false;
        }

        for (String exception : exceptions.trim().split(SPLIT_REGEX)) {
            if (type.getName().equalsIgnoreCase(exception) || type.getSimpleName().equalsIgnoreCase(exception)) {
                return true;
            }
        }

        return false;
    }

}
